<?php
/**
 * DonReY Framework 2012
 * Requires PHP version 5.3 or higher
 *
 * @author Wavetrex <wavetrex@gmail.com>
 * @link http://www.donrey.net/Documentation/
 * @license [GPL v3] http://www.gnu.org/licenses/gpl-3.0.html
 * @version 1.0.dev
 * @package DonReY
 */
namespace DRY {

/**
 * DonReY Framework 2012 :: core
 * The class name is intentionally left lowercase ( core instead of Core ), to signify that it's a fully static class (all methods and members are static)
 *
 * @author Wavetrex <wavetrex@gmail.com>
 * @link http://www.donrey.net/Documentation/Structure/core.html
 * @license [GPL v3] http://www.gnu.org/licenses/gpl-3.0.html
 * @version 1.0.dev
 * @package DonReY
 * @subpackage core
 */
class core
{
	// Exception/Error codes
	const
		EXC_UNKNOWN_ERROR = 0,
		EXC_CONFIG_FILE_ERROR = 1,
		EXC_AUTOLOAD_FILENOTFOUND = 3,
		EXC_AUTOLOAD_DENIEDLOAD = 4,
		EXC_EXTERNAL_LIB_MISSING = 5,
		EXC_BAD_PATH_CONSTANT = 6,

		EXC_INVALID_MODULE_REFERENCED = 10,
		EXC_MODULE_NOT_FOUND = 11,
		EXC_MODULE_INITIALIZATION_FAIL = 12,
		EXC_MODULE_DEPENDENCY_NOT_SET = 13,

		EXC_PLUGIN_METHOD_NOT_FOUND = 21,
		EXC_PLUGIN_METHOD_ALREADY_REGISTERED = 22,

		ERR_FILE_MISSING = 100,
		ERR_INVALID_XML = 101,
		ERR_INVALID_JSON = 102
		;

/*
 * ===== Initialization and Configuration =====
 */

	/**
	 * Application configuration, global
	 *
	 * @var SimpleXMLPlus
	 */
	public static $CFG;

	/**
	 * Global initialization - This is run by the bootstraper to load the project-configuration and initialize the framework
	 *
	 * @param string $config_file
	 * @param int $debug_record_level When larger than 0, all debug output is recorded internally by Water, to be displayed in case of a crash
	 * @throws Water
	 */
	public static function _init($config_file, $debug_record_level = 0)
	{
		Water::_init($debug_record_level);

		try { // to load the configuration file
			$error_data = array(
				'type' => 'XML',
				'config_file' => $config_file
			);

			if(!file_exists($config_file)) {
				$error_data['reason'] = Water::read_Message('core', self::ERR_FILE_MISSING, array('config_file'=> $config_file));
			} else {
/*
				// Test load using DOMDocument;
				$config_DOM = new DOMDocument;
				if($config_DOM-> load(realpath($config_file), LIBXML_NOCDATA|LIBXML_NOERROR|LIBXML_NOWARNING|LIBXML_NOBLANKS)==false) {
					$error_data['reason'] = [self::$exc_text['core'][self::ERR_INVALID_XML]; - TODO: Replace]
				} else {
					// Perhaps add Schema validation some time in the future ( I doubt it will happen tho )
					if($config_DOM-> schemaValidate(CORE_PATH. "/project-config.xsd")==false) {
						$error_data['reason'] = [self::$exc_text['core'][self::ERR_INVALID_XML]; - TODO: Replace]
					}
					self::$CFG = simplexml_import_dom($config_DOM, 'SimpleXMLPlus');
				}
*/
				// Original load using SimpleXML
				if((self::$CFG = simplexml_load_file($config_file, 'SimpleXMLPlus', LIBXML_NOCDATA|LIBXML_NOERROR|LIBXML_NOWARNING|LIBXML_NOBLANKS))===false) {
					$error_data['reason'] = Water::read_Message('core', self::ERR_INVALID_XML, array('config_file'=> $config_file));
				}

			}
			// if there was an error, throw exception
			if(isset($error_data['reason'])) {
				throw new Water('core', self::EXC_CONFIG_FILE_ERROR, $error_data);
			}

		} catch(\Exception $e) {
			// Crash without a valid config
			self::_crash();
		}

		// core autoloading - Paths for Modules and Plugins (and perhaps other things in the future)
		spl_autoload_register('DRY\\core::__autoload');

		if(isset(self::$CFG-> core-> paths))
			// custom paths
			$paths = self::assemble_Absolute_Paths(self::$CFG-> core-> paths, 'CORE_PATH');
		else
			// default paths (primary modules and built-in plugins)
			$paths = array(
				'modules'=> array(
					realpath(CORE_PATH.'/modules/')
				),
				'plugins'=> array(
					realpath(CORE_PATH.'/plugins/')
				)
			);

		// register all paths
		foreach($paths as $pathtype=> $pathlist) {
			switch($pathtype) {
				// All modules must be in namespace DRY\Module\ and have their file names end with .module.php
				case 'modules':
					foreach($pathlist as $path) {
						self::register_Autoloader_Path('DRY\\Module\\', $path.'/', '.module.php');
					} break;
				// All plugins must be in namespace DRY\Plugin\ and have their file names end with .plugin.php
				case 'plugins':
					foreach($pathlist as $path)
						self::register_Autoloader_Path('DRY\\Plugin\\', $path.'/', '.plugin.php');
					break;
			}
		}

		// global error handling
		set_error_handler(array('\\DRY\\Water', 'handler_Errors'));
		set_exception_handler(array('\\DRY\\Water', 'handler_Exceptions'));
		register_shutdown_function(array('\\DRY\\Water', 'handler_Shutdown'));

		// buffer output when debugging
		if(self::$CFG-> core['outputOnCrash']=='hide' || (self::$CFG-> core['debug'] && self::$CFG-> core['debug']!='disabled'))
			ob_start();

		// read module configuration ( and autoload modules if required )
		self::setup_Module_Config();
	}

/*
 * ===== Universal Class Autoloader =====
 * Can be configured to autoload any type of object, usually module components but is flexible enough for using it on user's code as well
 */

	/**
	 * Array of prefixes with arrays of paths in which class files are searched for
	 * @var array[array]
	 */
	private static $autoloader_paths = array();

	/**
	 * Universal class autoloader register method.
	 * Note - it doesn't check if a path is registered twice for the same prefix (or more), but if happens, it doesn't hurt either, just wastes time checking the same path multiple times
	 *
	 * @param string $class_prefix Prefix used in class name -> sent to __autoload method
	 * @param string $path Possible path where a class file might exist for that specific class ( everything after Puffix becomes file name, lowercased )
	 * @param string $file_suffix Added to the file name (normally just the .php extension)
	 */
	public static function register_Autoloader_Path($class_prefix, $path, $file_suffix = '.php', $check_function = null)
	{
		if(!isset(self::$autoloader_paths[$class_prefix]))
			self::$autoloader_paths[$class_prefix] = array();

		self::$autoloader_paths[$class_prefix]['__cfg__']['suffix'] = $file_suffix;
		if(is_callable($check_function))
			self::$autoloader_paths[$class_prefix]['__cfg__']['check_fn'] = $check_function;
		self::$autoloader_paths[$class_prefix][] = $path;
	}

	/**
	 * SPL Registered autoload function. Called when attempting to load any class for which prefixes have been registered with possible search paths
	 * (or an external library's object is being instanced)
	 * @param string $class_name
	 * @todo Internal exceptions
	 */
	public static function __autoload($class_name)
	{
		// try external libraries first
		$xml_class_name=str_replace('\\', '.', $class_name);
		if(isset(self::$CFG-> core-> libs) && isset(self::$CFG-> core-> libs-> $xml_class_name)) {
			$rel_path = constant((self::$CFG-> core-> libs-> {$xml_class_name}['rel']?:'LIB_PATH'));
			if(($fullpath = realpath($rel_path.self::$CFG-> core-> libs-> $xml_class_name))!==false) {
				require_once $fullpath;
				Water::dump($class_name, "core::__autoload (External)");
				// return once found the external library and included it
				return;
			} else
				throw new Water('core', self::EXC_EXTERNAL_LIB_MISSING, array('class'=>$class_name));
		}

		// detect which class name prefix to use
		$prefixes = array_keys(self::$autoloader_paths);

		// find the longest prefix match, which should be the correct one.
		$plen = 0; $correct_prefix = '';
		foreach($prefixes as $prefix) {
			$l = strlen($prefix);
			if(substr(ltrim($class_name,'\\'),0,$l)==$prefix) { // a matching prefix was found, check to see if it's the longest so far
				if($l > $plen) {
					$correct_prefix = $prefix;
					$plen = $l;
				}
			}
		}

		if($plen > 0) {
			// see is this class is allowed to load
			if(isset(self::$autoloader_paths[$correct_prefix]['__cfg__']['check_fn'])) {
				$check_fn = self::$autoloader_paths[$correct_prefix]['__cfg__']['check_fn'];
				if(call_user_func($check_fn, strtolower(substr(ltrim($class_name,'\\'), $plen)))===false)
					throw new Water('core', self::EXC_AUTOLOAD_DENIEDLOAD, array('class'=>$class_name));
			}
			// calculate filename and try all paths
			$filename = strtolower(substr(ltrim($class_name,'\\'), $plen)).self::$autoloader_paths[$correct_prefix]['__cfg__']['suffix'];
			foreach(self::$autoloader_paths[$correct_prefix] as $path) {
				if(is_array($path)) // skip the config var
					continue;
				if(($fullpath = realpath($path.$filename))!==false) { // found it !
					include_once $fullpath;
					Water::dump($class_name, "core::__autoload");
					// return when found the class' location
					return;
				}
			}
			// if we got here, the file was not found
			throw new Water('core', self::EXC_AUTOLOAD_FILENOTFOUND, array('class'=>$class_name));
		}

	}

/*
 * ===== Module System =====
 * Core's main function, to load DonReY Modules and provide access to them from any part of the application
 */

	/**
	 * Array of module objects that have been attached to the core
	 * @var array[\DRY\Module\base]
	 */
	public static $modules = array();

	/**
	 * List of System modules (External core components)
	 * Some are static, but since they are modules, they are autoloaded just like any other module
	 * @var array
	 */
	private static $system_modules = array('XML'=> true);

	/**
	 * Contains the order of loading the plugins (used by the core::unload_all() function)
	 *
	 * @var array
	 */
	private static $module_load_order;

	/**
	 * Loads the module configuration into memory, but doesn't actually load modules ( unless they have have the "autoload" attribute ) - Lazy Initialization
	 * CFG ->core ->modules
	 */
	private static function setup_Module_Config()
	{
		if(!isset(self::$CFG-> core-> modules))
			return; // nothing to do here, this "application" doesn't use any modules ?

		// register user module objects (which are loaded on first in self::$modules)
		foreach(self::$CFG-> core-> modules-> children() as $moduleCFG) {

			$alias = (string)$moduleCFG['alias']?:$moduleCFG-> getName();
			self::$modules[$alias] = array(
				'class'=> '\\DRY\\Module\\'.ucfirst($moduleCFG-> getName()),
				'type'=> (string)$moduleCFG['type']?:'standard',
				'autoload'=> $moduleCFG['autoload']=='true'?:false,
				'config'=> $moduleCFG-> children()
			); // module exists but not loaded yet. This is a reference to what must be done when module is actually asked for ( lazy loading )

			// module dependencies (other modules)
			$dependencies = $moduleCFG-> children("urn:donrey:core")-> dependencies;
			if($dependencies) {
				self::$modules[$alias]['dependencies'] = array();
				foreach($dependencies-> children() as $dep_key=> $dep_alias)
					self::$modules[$alias]['dependencies'][$dep_key] = (string)$dep_alias;
			}

			// module plugins (independent classes)
			$plugins = $moduleCFG-> children("urn:donrey:core")-> plugins;
			if($plugins) {
				self::$modules[$alias]['plugins'] = array();
				foreach($plugins-> children() as $plugin_name=> $plugin_config)
					self::$modules[$alias]['plugins'][$plugin_name] = $plugin_config;
			}

		}

		// check for modules which have "autoload" attribute, and load them (this overrides lazy load)
		foreach(self::$modules as $alias=> &$module) {
			if(is_array($module) && $module['autoload']) {
				$module = self::load_Module($alias);
			}
		}

		// register system modules - Always available, they are part of the core, however they are only loaded at first use.
		// To add another system module, core.php must be modified, as there is no external configuration for those.
		foreach(self::$system_modules as $sysmod=> $config) {
			self::$modules[$sysmod] = array(
				'class'=> 'Module_'.$sysmod,
				'type'=> 'system',
				'config'=> $config
			);
		}
	}

	/**
	 * Returns a reference to a module registered under $alias
	 * @param string $alias
	 * @param array $args
	 */
	public static function &m($alias, $args=array())
	{
		$alias = (string)$alias;

		// if the alias doesn't exist, it means it's not specified in CFG
		if(!isset(self::$modules[$alias])) {
			throw new Water('core', self::EXC_INVALID_MODULE_REFERENCED, array('alias'=> $alias));
		}
		// if module object doesn't exist (but the index into alias table exists), attempt to load the module
		if(!self::$modules[$alias] instanceof \DRY\Module\base) {
			self::load_Module($alias, $args);
		}
		// if at this point the self::$modules[$alias] is still not a valid module, something bad has happened ( missing module file, crashed in initialization, etc. )
		if(!isset(self::$modules[$alias]) || !self::$modules[$alias] instanceof \DRY\Module\base) {
			// erases the failed module object, or it's configuration, to prevent an attempt to load it again ( if it failed in the first time for any reason )
			// this only happens for an alias, it is possible that the same module CAN load with a different alias ( with other configuration parameters )
			unset(self::$modules[$alias]);
			throw new Water('core', self::EXC_MODULE_INITIALIZATION_FAIL, array('alias'=> $alias));
		}
		return self::$modules[$alias]; // this might return null if load_Module failed to find the existing module
	}

	/**
	 * Same as m(), but take the module name from the parameter
	 * @param string $alias
	 * @param array $args
	 */
	static function __callStatic($alias, $args)
	{
		return self::m($alias, $args);
	}

	/**
	 * Loads a module into the core, by using it's config defined alias. This function is called indirectly when accessing a module, if the module wasn't already loaded.
	 *
	 * @param string $alias
	 * @param array $extra
	 */
	private static function load_Module($alias, $extra=array())
	{
		// does nothing if a module with the specified alias is already loaded
		if(isset(self::$modules[$alias])&&self::$modules[$alias] instanceof \DRY\Module\base)
			return;
		// if the alias doesn't exist, it means it's not specified in CFG
		if(!isset(self::$modules[$alias])) {
			throw new Water('core', self::EXC_INVALID_MODULE_REFERENCED, array('alias'=> $alias));
		}

		// checks if a module depends on other modules, and recursively load them
		if(isset(self::$modules[$alias]['dependencies']))
			foreach(self::$modules[$alias]['dependencies'] as $dep_key=> $dep_alias)
				self::load_Module($dep_alias);

		$module_class = self::$modules[$alias]['class'];

		// we got so far, module file exists, class exists, now attempt to instantiate it - Autoloader should crash the framework if module cannot be found
		try {
			// module constructor receives: it's Alias, It's own CFG, Extra data passed to load_Module
			$module = new $module_class($alias, self::$modules[$alias]['config'], $extra);

			self::$module_load_order[] = $alias;
			self::$modules[$alias] = &$module;
		} catch (\Exception $e) {
			// nope, failed, disable the alias
			unset(self::$modules[$alias]);
			return;
		}
	}

	/**
	 * Helper function which will access configuration and load the proper dependencies for an instanced module, and also load preset plugins
	 * Note: This only works when called from the module's constructor !
	 * @param \DRY\Module\base $module
	 */
	public static function load_Dependencies_and_Plugins(Module\base &$module, $alias)
	{
		if(self::$modules[$alias] instanceof Module\base)
			return; // nothing to do now, module has been already instanced

		// create the dependencies references
		if(isset(self::$modules[$alias]['dependencies'])) {
			foreach(self::$modules[$alias]['dependencies'] as $dep_key=> $dep_alias) {
				$module[$dep_key] = $dep_alias;
			}
		}
		// load plugins and setup their arguments
		if(isset(self::$modules[$alias]['plugins'])) {
			foreach(self::$modules[$alias]['plugins'] as $plugin_name=> $arguments_xml)
				$module-> use_Plugin($plugin_name, $arguments_xml);
		}
	}

	/**
	 * Removes the module from the memory and ( if no other references exist to the module ) also calls the destructor.
	 * Calls for all modules the function self_clean() in case one exists
	 *
	 * @param string $alias
	 */
	private static function unload_Module($alias)
	{
		if(isset(self::$modules[$alias]) && self::$modules[$alias] instanceof \DRY\Module\base) {
			// only try to use this array element as a DRY_Module if it is actually a module object...
			if(method_exists(self::$modules[$alias],'self_Clean'))
				self::$modules[$alias]-> self_Clean();
		}
		// if this is the last in-memory copy of a module, it will also trigger any __destruct() method present inside it
		unset(self::$modules[$alias]);
	}

	/**
	 * Unloads all modules from the core. Use this before script end to allow proper order of destruction for all modules
	 * (Last created, First destructed)
	 */
	public static function unload_All_Modules()
	{
		if(empty(self::$module_load_order))
			return; // nothing to unload

		$reverse_load_order=array_reverse(self::$module_load_order);
		foreach($reverse_load_order as $alias) {
			self::unload_Module($alias);
		}
		// a second call to this method is pointless
		self::$module_load_order = array();
	}

	/**
	 * @todo Generates a visible error message and stops the execution.
	 * This will execute when it is no longer possible to recover from errors
	 */
	public static function _crash()
	{
		// TODO: Send the crash to any registered debug driver.

		// shutdown any loaded modules (even in case of a crash)
		self::unload_All_Modules();
//
		if(!self::$CFG || self::$CFG-> core['debug']=='internal' || self::$CFG-> core['debug']=='all') {
//			// Assamble crash display data
//			$crash = array(
//				'title'=> self::read_Message('default', 'CrashTitle'),
//				'errors'=> array()
//			);

			if(self::$CFG instanceof \SimpleXMLElement) {
				if(self::$CFG-> core['outputOnCrash']=='show')
					$crash['output'] = ob_get_clean();
				else
					ob_end_clean();
			}
//
//			// The design of the crash page is in the included file
//			include "core_crash.phtml";
		}

		if(self::$CFG instanceof \SimpleXMLElement) {
			if(self::$CFG-> core['debug']=='external') {
				if(self::$CFG-> core['outputOnCrash']=='show')
					ob_end_flush();
				else
					ob_end_clean();
			}
			if(self::$CFG-> core['debug']=='disabled' && self::$CFG-> core['outputOnCrash']=='hide') {
				ob_end_clean();
			}
		}
		die -1;
	}

/*
 * ===== Observer Design Pattern (Similar to SplObserver, but applied dynamically to objects) =====
 * Usually used for standardized communication between modules or other objects
 * Supports recording of multiple sets of Subjects and Observers, either accessed using overloading then _attach,_detach,_notify or using direct methods
 */

	/**
	 * Observer and subject sets
	 * @var array[SplObjectStorage]
	 */
	private static $_observer_sets = array();

	/**
	 * Registered subjects (references)
	 * @var array[object]
	 */
	private static $_subjects = array();

	/**
	 * Transforms an existing object as a "Subject" - While this works exactly like SplObserver/SplSubject suggests, it's not using those interfaces
	 * Any object can become a subject, and any object can be an observer
	 *
	 * @param string $id
	 * @param object $subject
	 * @param string $observer_update_method_name
	 * @return bool Result of operation
	 */
	public static function register_Subject($id, $subject)
	{
		if(isset(self::$_observer_sets[$id]))
			return false; // error, ID already exists

		self::$_observer_sets[$id] = new \SplObjectStorage();
		$_observers = &self::$_observer_sets[$id];

		// decorate the "subject" with the Observer Pattern specific functions (they start with underscore to avoid conflicts with existing variables in the class)

		/**
		 * Attaches an observer
		 * @param object $observer
		 * @param string $observer_update_method_name
		 */
		$subject->_attach = function($observer, $observer_update_method_name = 'update') use ($_observers)
		{
			$_observers[$observer] = $observer_update_method_name;
		};
		/**
		 * Detaches an observer
		 * @param object $observer
		 */
		$subject->_detach = function($observer) use ($_observers)
		{
			unset($_observers[$observer]);
		};
		/**
		 * Notifies all attached observers
		 */
		$subject->_notify = function() use ($_observers, $subject)
		{
			$arg_arr = func_get_args();
			array_unshift($arg_arr, $subject);
			foreach($_observers as $observer)
				call_user_func_array(array($observer, $_observers[$observer]), $arg_arr);
		};
		self::$_subjects[$id] = &$subject;
		return true; // success
	}

	/**
	 * Returns a stored subject by it's ID (which has the new closures inside it)
	 * @param string $id
	 */
	public static function get_Subject($id)
	{
		return isset(self::$_subjects[$id])?self::$_subjects[$id]:null;
	}

	/**
	 * Method variant for attaching an observer
	 * @param string $set
	 * @param object $observer
	 */
	public static function attach_Observer($id, $observer, $observer_update_method_name = 'update')
	{
		call_user_func(self::$_subjects[$id]->_attach, $observer, $observer_update_method_name);
	}

	/**
	 * Method variant for detaching an observer
	 * @param string $set
	 * @param object $observer
	 */
	public static function detach_Observer($id, $observer)
	{
		call_user_func(self::$_subjects[$id]->_detach, $observer);
	}

	/**
	 * Method variant for notifying an observer. Passes all function arguments to the "update" method of the observer
	 */
	public static function notify_Observers()
	{
		$arg_arr = func_get_args();
		$set = array_shift($arg_arr);
		call_user_func_array(self::$_subjects[$set]->_notify, $arg_arr);
	}

/*
 * ===== Extra helper functions =====
 * (which are usually used by modules, but they can be used by the application as well)
 */

	/**
	 * Creates a list of absolute paths using a config Element which contains paths inside it:
	 * - element name = index in returned array
	 * - element content = relative path
	 * - "rel" attribute = path constant to use ( CORE_PATH, APP_PATH, LIB_PATH, or used defined (MY_PATH?) )
	 * @param SimpleXMLElement $CFG_paths
	 * @param string $rel_default Which path constant to use if the "rel" attribute is not set
	 * @return array
	 */
	public static function assemble_Absolute_Paths(\SimpleXMLElement $CFG_paths, $rel_default = 'APP_PATH')
	{
		$paths = array();
		foreach($CFG_paths-> children() as $path) {
			$constant_name = (string)($path['rel']?:$rel_default);
			if(!defined($constant_name))
				throw new Water('core', self::EXC_BAD_PATH_CONSTANT, array('constant'=>$constant_name));
			$rel_path = constant($constant_name);

			// Note: this might be empty if realpath doesn't find a valid path. Receiving code must check each path individually
			if(!isset($paths[$path-> getName()]))
				$paths[$path-> getName()] = array();
			$paths[$path-> getName()][] = realpath(constant($constant_name).$path);
		}
		return $paths;
	}

}


/**
 * Mini templating system. Used by internal debugging to display error/warning/notice messages, but can be used externally as well.
 *
 * @author Wavetrex <wavetrex@gmail.com>
 * @link http://www.donrey.net/Documentation/Core/template.html
 * @license [GPL v3] http://www.gnu.org/licenses/gpl-3.0.html
 * @version 1.0.dev
 * @package DonReY
 * @subpackage core
 */
class Template
{
	private $mode;

	private $template;

	private $data;

	private $extra;

	/**
	 * Mini-template object initialization
	 * @param string $mode Mode of operation
	 * @param string $template Template used in the process
	 * @param mixed $data Data to be inserted in the template
	 * @param mixed $extra Extra information for certain modes
	 */
	public function __construct($mode = 'basic', $template, $data='', $extra=null)
	{
		$this-> mode = $mode;
		$this-> template = $template;
		$this-> data = $data;
		$this-> extra = $extra;
	}

	/**
	 * Replaces {placeholders} in a template string with values from an array or object
	 * The placeholders can be {simple} or {using.dot.syntax}, which translate into deeper levels into the array or object
	 *
	 * If the placeholder is not found in the data, it is not displayed.
	 * This type of renderer transformation engine can be used as a simple (yet effective) templating system.
	 *
	 * @param string $string The string in which {placeholders} are replaced
	 * @param array|object $data Data to be used as replacements
	 */
	public function string_Replacer($data)
    {
    	if(empty($this-> template)||!is_string($this-> template))
    		return '';
    	// This algorithm is inspired by a JavaScript code found on the net,
        //   from a guy named Ruben, which has improved on the String.prototype.present
        //   function created by Douglas Crockford
	    return preg_replace_callback(
	    	'/\{([^}|]*)(?:\|?(.*?))}/',
	    	function($matches) use ($data)
		    {
	            // search in array|object the proper key (recursive)
	            $m = explode('.',$matches[1]);
	            foreach($m as $segment) {
	            	if(is_object($data)) {
		                if(isset($data-> $segment))
		                    $data = $data-> $segment;
		                else // not found !
		                    return $matches[2]; // the optional default value when data is not found
	            	} elseif(is_array($data)) {
		                if(isset($data[$segment]))
		                    $data = $data[$segment];
		                else
		                    return $matches[2];
	            	} else
	            		return $matches[2]; // we still have segments to search, but data is something else, not object, not array - does not contain keys (or member names)
	            }
	            return (string)$data; // all segments processed, the searched data was found. If it can be converted to string, it will, if not... some error will occur
		    },
		    $this-> template
        );
	}

	/**
	 * Converts the template and data into a string, depending on mode
	 */
    public function __toString()
    {
    	$s = '';
    	switch($this-> mode) {
    		// basic string replacer ( direct correspondence between placeholders and data )
    		default:
    		case 'basic':
    			$s = $this-> string_Replacer($this-> data);
    			break;
    		// only displays the template if data is non-empty
    		case 'nonempty':
    			if(!is_null($this-> data) && count($this-> data))
    				$s = $this-> string_Replacer($this-> data);
    			break;
    		// assumes "data" is an array of sub-data, and replaces in the template for each sub-data
    		case 'loop':
    			foreach($this-> data as &$element) {
    				$s.= $this-> string_Replacer($element);
    			}
    			break;
    		// same as 'loop', however data is a direct array of values; 'extra' is the key used in template - Used for very simple loops with just one single value
    		case 'quickloop':
    			foreach($this-> data as &$element) {
    				$assambled_data = array($this-> extra => $element);
    				$s.= $this-> string_Replacer($assambled_data);
    			}
    			break;
    	}
    	return $s;
    }
}

// default language for framework debug/error message output
if(!defined("WET_MESSAGE_LANGUAGE"))
	define('WET_MESSAGE_LANGUAGE', "english");

/**
 * DonReY internal debugging system. Extends \Exception
 *
 * Class name comes from a play of words. Throwing Water on a DRY (Don't Repeat Yourself) object makes it wet ( no longer dry )
 * Example: throw new Water('core', core::EXC_UNKNOWN_ERROR, $data)
 *
 * @author Wavetrex <wavetrex@gmail.com>
 * @link http://www.donrey.net/Documentation/Core/core.html
 * @license [GPL v3] http://www.gnu.org/licenses/gpl-3.0.html
 * @version 1.0.dev
 * @package DonReY
 * @subpackage core
 */
class Water extends \Exception
{

	/**
	 * Messages that are being output by the framework in various occasions, in an array of zones and codes
	 * (this data is filled by including one or more "messages" files)
	 * @var array
	 */
	public static $messages = array();

	protected static $start_timer;

	/**
	 * Initialization of the debugging system
	 */
	public static function _init($record_level = 0)
	{
		// load the primary messages file
		require_once(CORE_PATH. '/messages/'.WET_MESSAGE_LANGUAGE.'.messages.php');

		// micro-timer start
		self::$start_timer = \microtime(true);

		self::$record_output_level = $record_level;
	}

	/**
	 * Access into the Messages array while inserting specified $data into the message
	 *
	 * @param string $category
	 * @param int $code
	 * @param mixed $data
	 */
	public static function read_Message($category, $code, $data = null)
	{
		if(!array_key_exists($category, self::$messages)) {
			// missing category, use the default message
			$message = self::$messages['default']['DefaultError'];
		} else {
			// check if code exist for a category
			if(array_key_exists($code, self::$messages[$category])) {
				$message = self::$messages[$category][$code];
			} else {
				// try to load default message for that category (index 0), if it exists
				if(isset(self::$messages[$category][0]))
					$message = self::$messages[$category][0];
				else
					// no, back down to the general default
					$message = self::$messages['default']['DefaultError'];
			}
		}
		return (string)new Template('basic', $message, $data);
	}


	/**
	 * Debugging output, contains all dumps, recorded errors, thrown exceptions and other debug-related output that went through the internal debugging system
	 * Key = microtime() difference since start of script, a floating number, normally very small, unless the Bug #48187 with microtime() and diff() hasn't been fixed yet.
	 * Value = The debugging related object, containing:
	 * - type (string): 'plain', 'notice', 'warning', 'error', 'exception'
	 * - data (mixed): Arbitrary php data ( can be simple integers or strings, arrays, objects, etc. ) - Optional
	 * - label (string): A label associated with the information ( must be a string or, be convertible to string ) - Optional
	 * - meta (array): Metadata containing original location of the debug output:
	 * 		'File'=>{string},	// File in which the debug output was generated
	 * 		'Line'=>{int},		// Line where the debug output was generated
	 * 		'Class'=>{string},	// Class inside the File which contains the Line
	 * 		'Method'=>{string},	// Method inside the File which contins the Line
	 * 		'Module'=>{DRY Module|core} - Which donrey module generated the debug message, in case it's a message from inside the framework - and not the user's app (optional)
	 * PHP generated notices/warnings/errors/exceptions are also recorded here using handlers (self::handler_*)
	 *
	 * @var array
	 */
	public static $output = array();

	/**
	 *
	 * Enter description here ...
	 * @var int
	 */
	private static $record_output_level = 0;

	/**
	 * Sets the debug output recording level:
	 *  0 - nothing is recorded, only sent to attached debuggers (if there's none, no output at all, blank page in case of crash)
	 *  1 - critical errors are recorded (errors and exceptions)
	 *  2 - warnings are also recorded
	 *  3 - notices and var dumps are recorded
	 * @param int $level
	 */
	public static function set_Record_Level($level)
	{
		self::$record_output_level = $level;
	}

	public static function send_Output(&$output_element, $time = null)
	{
		// send the exception to all registered debuggers
		$time = microtime(true) - self::$start_timer;
		foreach(self::$debuggers as &$dd) {
			$dd-> output($output_element, $time);
		}

		// and record into internal if asked to ( for use with Crash() )
		if(!self::$record_output_level)
			return; // nothing if level = 0
		switch($output_element-> type) {
			case 'exception':
			case 'error':
				if(self::$record_output_level<1)
					return;
			case 'warning':
				if(self::$record_output_level<2)
					return;
			case 'notice':
			case 'plain':
				if(self::$record_output_level<3)
					return;
		}

		if(is_null($time))
			$time = microtime(true) - self::$start_timer;
		self::$output[(string)$time] = $output_element;
	}

	/**
	 * Array of registered debuggers (drivers). All debug functions will send data to those as well
	 *
	 * @var array[\DRY\Debug\Driver_base]
	 */
	private static $debuggers = array();

	/**
	 * Register a debug driver under an unique key, to be used by core when outputting debug info
	 *
	 * @param string $key
	 * @param \DRY\Debug\Driver_base $debug_driver
	 * @param array $features Unused at the moment
	 */
	public static function register_Debugger($key, \DRY\Debug\Driver_base $debug_driver, $features=array())
	{
		if(isset(self::$debuggers[$key]))
			return false; // already registered

		self::$debuggers[$key] = &$debug_driver;
	}

	/**
	 * Removes a debug driver from the list of registered ones
	 *
	 * @param string $key
	 */
	public static function unregister_Debugger($key)
	{
		unset(self::$debuggers[$key]);
	}

	/**
	 * Dumps an arbitrary content into de debugging system
	 *
	 * @param mixed $data The data that will be dumped
	 * @param string $label Optional - Label to associate with the dump
	 * @param array $meta Metadata indicating the origin of the dump. If missing, the method attempts to detect the location using debug_backtrace()
	 * @param string $type 'plain', 'notice', 'warning' or 'error' (for caught exceptions use self::caught_Exception )
	 */
	public static function dump($data, $label = '', $meta = null, $type = 'plain')
	{
		// dumps nothing if debugging is disabled
		if(core::$CFG && (!isset(core::$CFG-> core['debug']) || core::$CFG-> core['debug']=='disabled'))
			return;
		// Identify location from where dump was called (file, line, class and function)
		if(!is_array($meta)) {
			$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); $dump_found = false;
			foreach($trace as $entry) {
				if($entry['class']=='DRY\Water'&&$entry['function']=='dump') { // found the call point of dump()
					// File and Line are from the entry itself
					$meta = array(
						'File'=> $entry['file'],
						'Line'=> $entry['line']
					);
					$dump_found = true;
					continue;
				}
				if($dump_found) {
					// Class and Function are from the next entry
					if(isset($entry['class']))
						$meta['Class'] = $entry['class'];
					if(isset($entry['function']))
						$meta['Function'] = $entry['function'];
					break;
				}
			}
		}

		// prepare the data to be dumped
		$output_element = new \stdClass();
		$output_element-> type = $type;
		$output_element-> data = $data;
		$output_element-> label = $label;
		$output_element-> meta = $meta;

		self::send_Output($output_element);
	}

	/**
	 * Used to display an exception as an error, usually in the catch() {} part of a try... catch ( so the exception is visible, but not stopping )
	 *
	 * @param \Exception $e The exception that has been caught
	 * @param string $category Category in the Messages array, to get the proper text for the exception
	 * @param int $code Code to locate error message in Messages array
	 * @param array|object $vars Data to be passed along, can be used to construct the exception message using \DRY\Template objects
	 * @param string $extra_msg Something to be appended to the standard built-in message (Optional)
	 */
	public static function caught_Exception(\Exception $e, $category = 'core', $code = 0, $vars = null, $extra_msg = null)
	{
		if($e instanceof Water)
			return; // do not display Water exceptions, since they have already been recorded

		// prepare the data to be shown
		$output_element = new \stdClass();
		$output_element-> type = 'error';
		$output_element-> meta = $vars['err'] = array(
			'File'=> $e-> getFile(),
			'Line'=> $e-> getLine(),
			'Code'=> $e-> getCode()
		);
		$vars['err']['Category'] = $category;
   		$output_element-> label = self::read_Message($category, $code, $vars);
   		if($extra_msg)
   			$output_element-> label.= " ({$extra_msg})";
		$output_element-> data = $e-> getMessage();

		self::send_Output($output_element);
	}

	/**
	 * Catches a standard exception not handled using try...catch, and records it, after that it calls any existing debugger module then displays the crash page
	 *
	 * @param Water $exception
	 */
	public static function handler_Exceptions(\Exception $e)
	{
		if($e instanceof Water)
			return; // do not display Water exceptions, since they have already been recorded

		// prepare the data to be dumped
		$output_element = new \stdClass();
		$output_element-> type = 'exception';
		$output_element-> data = $e;
		$output_element-> label = ''; // No label on exceptions
		$output_element-> meta = array(
			'File'=> $e-> getFile(),
			'Line'=> $e-> getLine(),
			'Code'=> $e-> getCode()
		);

		self::send_Output($output_element);
	}

	public static $e_types = array(
		// caught warnings, notices and recoverable errors
		E_WARNING=> "E_WARNING",
		E_USER_WARNING=> "E_USER_WARNING",
		E_STRICT=> "E_STRICT",
		E_NOTICE=> "E_NOTICE",
		E_USER_NOTICE=> "E_USER_NOTICE",
		E_DEPRECATED=> "E_DEPRECATED",
		E_USER_DEPRECATED=> "E_USER_DEPRECATED",
		E_USER_ERROR=> "E_USER_ERROR",
		E_RECOVERABLE_ERROR=> "E_RECOVERABLE_ERROR",
		// not caught, fatal errors
		E_ERROR=> "E_ERROR",
		E_PARSE=> "E_PARSE",
		E_CORE_ERROR=> "E_CORE_ERROR",
		E_COMPILE_ERROR=> "E_COMPILE_ERROR"
	);

	/**
	 * Error handler. Handles recording of Warnings, Notices, Errors and also transforms Errors into Exceptions, so they can be caught
	 *
	 * @param $errno Error number (predefined constants)
	 * @param $errstr Error string
	 * @param $errfile Where in the code it has occured
	 * @param $errline At which line
	 */
    public static function handler_Errors($errno, $errstr, $errfile, $errline)
    {
		// prepare the data to be dumped
		$output_element = new \stdClass();
		$output_element-> data = $errstr;
		$output_element-> label = self::$e_types[$errno];
		$output_element-> meta = array(
			'File'=> $errfile,
			'Line'=> $errline
		);

    	switch($errno) {
    		case E_WARNING:
    		case E_USER_WARNING:
    		case E_STRICT:
    			$output_element-> type = 'warning'; break;
    		case E_NOTICE:
    		case E_USER_NOTICE:
    		case E_DEPRECATED:
    		case E_USER_DEPRECATED:
    			$output_element-> type = 'notice'; break;
    		case E_USER_ERROR:
    			$crash = true;
    		case E_RECOVERABLE_ERROR:
    			$output_element-> type = 'error'; break;
    		default: // some other new error code in a future version of PHP ?
    			$output_element-> type = 'plain';
    			$crash = true; // allow the standard PHP error handler
       	}

		self::send_Output($output_element);
		if(isset($crash))
			return false;
	}

	/**
	 * Shutdown handler. Will always be called when script ends (this might allow catching critcal errors which cannot be handled by handler_Exceptions),
	 *   as well as showing the "crash" screen if enabled in configuration
	 * @todo handler_Shutdown needs improvments
	 */
	public static function handler_Shutdown()
	{
        if (is_array($error = \error_get_last())) {
        	switch($error['type']){
                case E_ERROR:
                case E_PARSE:
                case E_CORE_ERROR:
                case E_COMPILE_ERROR:
		        	$output_element = new \stdClass();
		        	$output_element-> type = 'error';
					$output_element-> data = $error['message'];
					$output_element-> label = self::$messages['default']['CrashTitle'].' '.self::$e_types[$error['type']];
					$output_element-> meta = array(
						'File'=> $error['file'],
						'Line'=> $error['line']
					);
					Water::send_Output($output_element);
					break;
            }
            // Crash !
            // TODO: Output a nice crash using the template ( core_crash.phtml ? )
			// shows the debug output in reverse order ( the last one that caused the crash displays first )
			$output = array_reverse(self::$output);

            echo "<table border='1' width='100%'>";
            echo "<tr><th>Time</th><th>Type</th><th>Label</th><th>Data</th><th>File</th><th>Line</th></tr>";
            foreach($output as $time=> $el) {
				$time = sprintf("%.3fms ", (float)$time * 1000);
            	echo "<tr><td>{$time}</td><td>{$el-> type}</td><td>{$el-> label}</td><td>{$el-> data}</td><td>{$el-> meta['File']}</td><td>{$el-> meta['Line']}</td></tr>";
            }
            echo "</table>";
        }

        core::unload_All_Modules();
	}

	/**
	 * Creates the exception and also log it
	 *
	 * @param string $category Category in the Messages array, to get the proper text for the exception
	 * @param int $code Exception code constant
	 * @param array|object $exc_data Data to be passed along, can be used to construct the exception message using \DRY\Template objects
	 * @param string $extra_msg Other custom text to be appended to the exception text
	 */
    public function __construct($category = 'core', $code = 0, $exc_data = null, $extra_msg = null)
    {
		// prepare the data to be shown
		$output_element = new \stdClass();
		$output_element-> type = 'exception';
		$output_element-> label = ''; // No label on exceptions
		$output_element-> meta = $exc_data['err'] = array(
			'File'=> $this-> file,
			'Line'=> $this-> line,
			'Water'=>  array(
				'Category'=> $category,
				'Code'=> $code
			)
		);

		// assamble the exception message using the strings from *.messages.php file(s)
		$exc_data['err']['Category'] = $category;
		$water_message = self::read_Message($category, $code, $exc_data);
		if($extra_msg)
			$water_message.= " ({$extra_msg})";

		// original exception constructor
		parent::__construct($water_message, $code);

		$output_element-> data = $this;
		self::send_Output($output_element);
    }

    /**
     * Converts the exception into a text.
     *
     * @return string
     */
    public function __toString()
    {
    	return $this-> message;
    }
}

}

namespace DRY\Module {

/**
 * DonReY Module Base Class
 *
 * @author Wavetrex <wavetrex@gmail.com>
 * @link http://www.donrey.net/Documentation/Modules/modules.html
 * @license [GPL v3] http://www.gnu.org/licenses/gpl-3.0.html
 * @version 1.0.dev
 * @package DonReY
 */
use DRY\Water;
use DRY\core;

abstract class base implements \ArrayAccess
{
	/**
	 * Loaded module configuration (once per alias of a module)
	 *
	 * @var SimpleXMLElement
	 */
	public $CFG;

	/**
	 * Alias on which this module appears (or the module name itself)
	 * @var string
	 */
	public $_alias;

	/**
	 * References to Module dependencies
	 * @var array[DRY\Module_base]
	 */
	private $_dependencies;

	/**
	 * General module constructor. Individual modules might want to override this
	 *
	 * @param string $alias
	 * @param SimpleXMLElement $config
	 */
	public function __construct($alias, $config)
	{
		$this->_alias = $alias;
		$this-> CFG = &$config;

		core::load_Dependencies_and_Plugins($this, $alias);

		// check if module needs the core autoloader, and set it up
		if(defined(get_class($this)."::Autoload_Directory"))
			core::register_Autoloader_Path(
				constant(get_class($this)."::Autoload_Prefix").'_',
				__DIR__.'/modules/'.constant(get_class($this)."::Autoload_Directory").'/',
				'.'.constant(get_class($this)."::Autoload_Suffix").'.php'
			);
	}

	/**
	 * Loaded plugin objects
	 * @var array[DRY\Plugin]
	 */
	private $_plugins = array();

	/**
	 * List of methods that the loaded plugins provide
	 * Key = Method name, Value = Plugin name (reference into $this->_plugins)
	 * @var array
	 */
	private $_plugin_Methods = array();

	/**
	 * Loads a plugin object into an internal repository and returns the object
	 *
	 * @param string $name Plugin name, from the Plugins repository
	 * @param mixed __args__ Arguments for constructor
	 * @return \DRY\Plugin
	 */
	public function &use_Plugin()
	{
		$args = func_get_args();
		$name = array_shift($args);
		if(!isset($this->_plugins[$name])) {
			$plugin_class = 'DRY\Plugin\\'.$name;
			// the plugin receives the module object in which it is loaded, plus the arguments of the use_Plugin method
			// so a plugin could use module dependencies and other loaded plugins!
			$plugin = new $plugin_class($this, $args);
			$this->_plugins[$name] = &$plugin;
			// registers the methods that this plugins provides. If there's a name conflict (method already registerd with another plugin), throws an exception
			if(isset($plugin::$_methods)) {
				foreach($plugin::$_methods as $method) {
					if(array_key_exists($method, $this->_plugin_Methods))
						throw new Water('core', core::EXC_PLUGIN_METHOD_ALREADY_REGISTERED, array('method'=> $method, 'plugin'=> $this->_plugin_Methods[$method]));
					$this->_plugin_Methods[$method] = $name;
				}
			}
		}
		return $plugin;
	}

	/**
	 * Overloader to call a method from one of the loaded plugins
	 *
	 * @param string $name Method name
	 * @param array $arguments
	 */
	public function __call($name, $arguments)
	{
		if(!array_key_exists($name, $this->_plugin_Methods))
			throw new Water('core', core::EXC_PLUGIN_METHOD_NOT_FOUND, array('method'=> $name));

		return call_user_func_array(array($this->_plugins[$this->_plugin_Methods[$name]], $name), $arguments);
	}

	/**
	 * Checks if a module on which this one depends has been instanced
	 * @param string $dep_key
	 */
	public function offsetExists($dep_key)
	{
		return isset($this->_dependencies[$dep_key]) && $this->_dependencies[$dep_key] instanceof base;
	}

	/**
	 * Access module dependencies using the dependency key
	 * @param string $dep_key
	 */
	public function offsetGet($dep_key)
	{
		if(!isset($this->_dependencies[$dep_key]) || !$this->_dependencies[$dep_key] instanceof base)
			throw new Water('core', core::EXC_MODULE_DEPENDENCY_NOT_SET, array('depkey'=> $dep_key));
		return $this->_dependencies[$dep_key];
	}

	/**
	 * Change the module used by a dependency key to another DRY Module
	 *
	 * @param string $dep_key
	 * @param string $module_alias
	 */
	public function offsetSet($dep_key, $module_alias)
	{
		$this->_dependencies[$dep_key] = core::m($module_alias);
	}

	/**
	 * Stops needing another module. In practice, this will never get used.
	 *
	 * @param string $dep_key
	 */
	public function offsetUnset($dep_key)
	{
		unset($this->_dependencies[$dep_key]);
	}

}

}

namespace {

// hack for php 5.3.x prior to 5.3.6 (which didn't have this constant):
if(!defined("DEBUG_BACKTRACE_IGNORE_ARGS"))
	define("DEBUG_BACKTRACE_IGNORE_ARGS", true);

/**
 * SimpleXMLPlus enhances SimpleXMLElement with some very useful functions (attaching, merging of objects)
 *
 * @author Wavetrex <wavetrex@gmail.com>
 * @link http://www.donrey.net/Documentation/Structure/core.html
 * @license [GPL v3] http://www.gnu.org/licenses/gpl-3.0.html
 * @version 1.0.dev
 * @package DonReY
 */
class SimpleXMLPlus extends SimpleXMLElement
{
	/**
	 * Creates an instance of SimpleXMLPlus in a simple way
	 *
	 * @param string $name
	 * @param array $attr Optional, list of attributes
	 * @param string $class Optional, class to create object ( when extending SimpleXMLPlus )
	 * @return SimpleXMLPlus created object
	 */
	static public function &create_Instance($name, $attr=null, $class=null)
	{
		if(is_null($class))
			$class = __CLASS__;
		$element = new $class("<?xml version='1.0'?><{$name}/>");
		// set attributes if they exist
		if(!is_null($attr) && is_array($attr))
			$element-> set_Attributes($attr);
		return $element;
	}

	/**
	 * Adds a new child to a SimpleXMLElement, when the new child is also a SimpleXMLElement
	 *
	 * @param SimpleXMLElement $child Child with content, attributes and it's own children
	 * @param string $child_name Allows renaming the child when adding it.
	 * @return SimpleXMLPlus reference to the new child in the current object
	 */
	public function &add_XML_Child(SimpleXMLElement &$child, $child_name=null)
	{
		$cstr=trim((string)$child);
		if(is_null($child_name))
			$child_name=$child->getName();
		$newchild=$this->addChild($child_name,strlen($cstr)>0?$cstr:null); // content from $child is already in the new child

		// add attributes to the element
		foreach($child->attributes() as $key=>$attr)
			$newchild->addAttribute($key,(string)$attr);

		// recursively attach children
		foreach($child->children() as $subchild)
			$newchild->add_XML_Child($subchild);

		return $newchild;
	}

	/**
	 * Merges the two SimpleXMLElement objects into a new one, overriding values from the first one in case of unsolvable conflicts
	 *
	 * @param SimpleXMLElement $xmlb
	 * @return SimpleXMLPlus - New SimpleXMLElement resulting from the combination of the two
	 */
	public function &merge(SimpleXMLElement &$xmlb)
	{
		// element name has to be identical
		if($this->getName()!=$xmlb->getName())
			return false; // error, non-identical names, cannot have two roots

		$element_name=$this->getName();

		// extract content from A and B
		$astr=trim((string)$this);
		$bstr=trim((string)$xmlb);

		// 1. set content: from A if B is empty, from B otherwise ( ignoring A )
		$class = get_class($this);
		$xmlnew=new $class("<?xml version='1.0'?><{$element_name}>".(strlen($bstr)>0?$bstr:$astr)."</{$element_name}>");

		// 2. merge attributes: keep the order of attributes, first A, but override from B if it exists
		foreach($this->attributes() as $name=>$value)
			if($name!="__xml_merge")
				$xmlnew->addAttribute($name,isset($xmlb[$name])?$xmlb[$name]:$value);
		// rest of attributes from xmlb that didn't exist in $this
		foreach($xmlb->attributes() as $name=>$value)
			if($name!="__xml_merge")
				if(!isset($xmlnew[$name])) {
					$xmlnew->addAttribute($name,$value);
				}

		// special attribute that dictates how children will be combined:
		// - if __xml_merge attribute exists, the children will be merged ( requires unique child element name, or it breaks )
		// - it it doesn't exist, children are just added, as is
		if(isset($this['__xml_merge'])||isset($xmlb['__xml_merge'])) {
			unset($this['__xml_merge'], $xmlb['__xml_merge']);
			$merge_mode=true;
		} else
			$merge_mode=false;

		// 3. merge children: keep order of children, first A and merge with any existing children from B, then add the children from B that did not exist in A
		if($merge_mode) {
			foreach($this->children() as $name=>$child)
				if(isset($xmlb->children()->$name)) { // if the child also exists in B merge them
					// create the merged child
					$mergedchild=$child->merge($xmlb->children()->$name);
					$xmlnew->add_XML_Child($mergedchild);
				} else // otherwise just attach the child to $xmlnew
					$xmlnew->add_XML_Child($child);
			// now the rest from B that did not exist in A
			foreach($xmlb->children() as $name=>$child)
				if(!isset($xmlnew->children()->$name))
					$xmlnew->add_XML_Child($child);
		} else { // stack mode
			foreach($this->children() as $name=>$child)
				$xmlnew->add_XML_Child($child);
			foreach($xmlb->children() as $name=>$child)
				$xmlnew->add_XML_Child($child);
		}

		return $xmlnew;
	}

	/**
	 * Sets all attributes() from an array, directly, overwriting existent one with the same name
	 *
	 * @param array $attributes
	 */
	public function set_Attributes(array $attributes)
	{
		foreach($attributes as $attrname=> $attrval)
			if(!isset($this->attrname))
				$this-> addAttribute($attrname, $attrval);
			else
				$this[$attrname] = $attrval;
		return $this-> attributes();
	}

	/**
	 * Converts attributes from a SimpleXMLElement to an array, where attrName becomes key and attrValue becomes value
	 * The function can also place some attributes in a second array, leaving those not found in $attr_extract in the first one
	 *
	 * @param array $array1 Where the attributes will be placed
	 * @param string $extract CSV list of keys that will be extracted
	 * @param array $array2 Where the extracted attributes will be placed
	 * @param string $ignore CSV list of keys that will be completely ignored (not placed in any of the two arrays)
	 * @return Number of elements that went into $array1
	 */
	public function attributes_To_Arrays(&$array1, $extract=NULL, &$array2=NULL, $ignore=NULL)
	{
		if(count($this->attributes())==0)
			return 0;

		if(!is_null($extract))
			$extracted=array_flip(explode(',',$extract));
		if(!is_null($ignore))
			$ignored=array_flip(explode(',',$ignore));

		$count=0;
		foreach($this->attributes() as $attrname=>$attrval) {
			if(!is_null($extract))
				if(isset($extracted[$attrname])) {
					$array2[$attrname]=(string)$attrval;
					continue;
				}
			if(!is_null($ignore)&&isset($ignored[$attrname]))
				continue;
			$array1[$attrname]=(string)$attrval;
			$count++;
		}
		return $count;
	}

	/**
	 * Exports this SimpleXMLPlus object into an array
	 *   Children become another array inside this one
	 *   Attributes become the special "__attributes" key in an array.
	 *   Scalar elements become array values
	 *   Text nodes in elements which have children and/or attributes become index "0" (numeric)
	 * @param array $array Array to place XML into (it's not necessary to be empty)
	 * @return string Name of this SimpleXMLPlus object
	 */
	public function to_Array(&$array)
	{
		// if only text node and, no children an no attributes, convert $array into a string
		if(count($this-> children())==0 && count($this->attributes())==0) {
			$array = (string)$this;
			return $this-> getName();
		}

		$multiple_children = array();

		// insert children as array keys
		if(count($this-> children()) > 0)
			foreach($this-> children() as $child_name=> $element) {
				if(!isset($array[$child_name])) {
					// first time this child was encountered
					$element-> to_Array($array[$child_name]);
				} else {
					// same array key has already been used ( multiple elements under the same name )
					if(!isset($multiple_children[$child_name])) {
						$array[$child_name] = array(
							$array[$child_name]
						);
						$multiple_children[$child_name] = true;
					}
					$new_sub_element = array();
					$element-> to_Array($new_sub_element);
					$array[$child_name][] = $new_sub_element;
				}
			}

		// insert attributes into special key "__attributes"
		if(count($this->attributes()) > 0) {
			$array['__attributes'] = array();
			foreach($this-> attributes() as $attr_name=> $attribute)
				$array['__attributes'][$attr_name] = (string)$attribute;
		}

		// text node(s) while children or attributes exist
		$textnodes = (string)$this;
		if(strlen($textnodes) > 0)
			$array[] = $textnodes;

		return $this-> getName();
	}
}

//===== Debug Helpers =====//

/**
 * A very simple debugging tool: var_dump() on screen, styled ( do not use for ajax responses )
 * Use ONLY when developing/testing, not for any live code
 *
 * @param $var
 * @param $title
 */
function dump($var,$title=null)
{
	// identify source of dump
	$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
	foreach($trace as $source) {
		if($source['function'] == 'dump')
			break;
	}
	// and dump the stuff
	echo '<div style="background-color:#F0F0F0; color:black; border-bottom:1px solid #7777AA; padding-left: 4px;">';
	if(!is_null($title)) echo "<b>{$title}</b> [{$source['file']} @ {$source['line']}]<br>";
	echo '<pre>'; var_dump($var); echo '</pre></div>';
}

/**
 * Debug Dump, forwards to Water::dump, integrated in the framework, follows the "debug" attribute - displays nothing if debug is disabled
 *
 * @param mixed $object
 * @param string $label
 */
function dd($object, $label=null)
{
	// identify source of dump
	$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
	foreach($trace as $entry) {
		if($entry['function'] == 'dd') {
			$source = array(
				'Line'=> $entry['line'],
				'File'=> $entry['file'],
			);
			break;
		}
	}
	DRY\Water::dump($object, $label, $source);
}

// TODO: Move these into some class, DonReY doesn't like global functions !

/**
 * Construct a key out of multiple levels of array keys, to help locate keys in identical arrays when using array_walk_recursive
 * (That didn't make much sense, but an example could...)
 *
 * @param array $array
 */
function array_walk_recursive_Create_KeyMap(&$array, $pkey = array(), &$keymap = array())
{
	foreach($array as $key=>&$value) {
		$ckey = array_merge($pkey, array($key));
		if(is_array($value))
			array_walk_recursive_Create_KeyMap($value, $ckey, $keymap);
		else
			$keymap[] = $ckey;
	}
	return $keymap;
}

/**
 * Searches a recursive array using a keymap item (which could have been created by the above function (array_walk_recursive_Create_KeyMap)
 * @param array $container
 * @param array $keymap_item
 */
function search_recursive_array_With_KeyMap(&$container, $keymap_item)
{
	foreach($keymap_item as $map)
		if(isset($container[$map]))
			$container = &$container[$map];
		else
			return null; // error, map wrong
	return $container;
}

/**
 * Adds sub-arrays to an array using a keymap item, and optionally places an item inside the newly created location
 *
 * @param array $container
 * @param array $keymap_item
 */
function &create_recursive_array_With_KeyMap(&$container, $keymap_item, &$item = null)
{
	foreach($keymap_item as $map) {
		if(!isset($container[$map]))
			$container[$map] = array();
		$container = &$container[$map];
	}
	if(!is_null($item))
		$container = $item;
	return $container;
}

}